from .bitset import BitSet

# 芯片类作为一个父类,需要能够实现的功能有
# 1、避免父类被使用，设定不重载不能使用的功能
# 2、设置引脚名称和引脚功能(IN/OUT)

# 修饰器作用是在执行前后增加功能

# 约束 : 0 时刻为处理时钟运行和芯片重置的时间
# 约束 : 1 时刻为芯片输出信息的时间
# 约束 : 2 时刻为芯片输入信息的时间

class Chip:
    def __init__(self, width, inmask, outmask):
        # inmask  是对 Chip 输入引脚切片，切片可以使用的类型请参考BitSet
        # outmask 是对 Chip 输出引脚切片，切片可以使用的类型请参考BitSet
        self._ref = BitSet(width)           # 整个芯片的引脚数据参考(所有引脚都为这个的引用)
        self.pinIN  = self._ref@inmask      # 输入引脚(从整个芯片中引用)
        self.pinOUT = self._ref@outmask     # 输出引脚(从整个芯片中引用)
    def __repr__(self):
        # 默认芯片引脚都来自芯片的_ref变量
        # 默认芯片数据都来自芯片的_dat变量，如果有则输出
        pin = "<引脚>:[{:<0b>}]".format(self._ref)
        try : dat = " <数据>:[{:<0b>}]".format(self._dat())
        except : dat = ""
        return pin + dat
    def __getitem__(self, key) : return self._ref[key]          # 对芯片类重构了索引方法
    def __setitem__(self, key, value) : self._ref[key] = value  # 可以方便的直接对芯片取值、赋值
    def __matmul__(self, key) : return self._ref@key            # 甚至是获得它的切片引用
    # 引脚绑定(连线)
    keyframes = {}      # 所有关键帧
    maxframe = 0        # 关键帧长度
    # 这里创建了一个装饰器，可以将芯片的关键帧包装到自己的类变量中
    @classmethod
    def keyframe(cls, frameNum):
        def wrapper(frame):
            if cls.keyframes == Chip.keyframes :                    # 处理在Chip被继承后
                cls.keyframes = dict(Chip.keyframes)                # 添加任意frame都将创建一个当前类的新frame列表
                if cls != Chip : del cls.keyframes[0]               # 并将Chip的默认帧添加到这个列表中
            if cls.maxframe < frameNum : cls.maxframe = frameNum    # 并在处理任意帧时完成最大帧的更新
            def wrapped_frame(chip):
                if not isinstance(chip, cls) :                      # 添加了类型检查
                    raise TypeError("KeyFrame需要获得属于他的类")
                else : return frame(chip)                           # 可以在遇到奇怪的类型错误时提示
            cls.keyframes[frameNum] = wrapped_frame
            return None
        return wrapper
    def update(self, frameNum=-1):
        if len(self.keyframes) == 1 : res = self.keyframes[-1](self)            # 如果帧列表里面只有1个帧，那么一定是默认帧
        if frameNum in self.keyframes : res = self.keyframes[frameNum](self)    # 判断当前编号是否在列表中
        else : res = self.keyframes[-1](self)                                   # 如果不在，则使用默认帧
        for n in self.__dict__ :                                                # 将类中找到的所有可更新项更新
            if type(self.__dict__[n]) == BitSet : self.__dict__[n].__update__()
        return res

# 芯片逻辑的第一帧
# 在keyframe中传入想重写的帧(Chip换成自己继承的子类)
# 只要有帧被重写(不一定是0帧)就会将这个提示去掉
# 通过重写帧完成部分芯片的同步/异步
# 芯片的同一帧将会依次更新，不确定顺序
# 所以推荐按照一定逻辑来设定
# 比如:
#       第0帧   处理时钟、重置等操作
#       第1帧   处理芯片的输出
#       第2帧   处理芯片的输入
# 这样可以避免一个芯片一次完成输入输出后
# 其他芯片的输出无法被导入到该芯片中
@Chip.keyframe(0)
def _frame(chip):
    print("\n\033[1;33m[[[芯片需要设置逻辑才能够被使用]]]")
    print("使用以下语法来重写芯片逻辑:")
    print('''
    @Chiptype.keyframe(x):
    def _frame(chip):
        # Things to do at Frame(x)
        # if next frame is avaliable, return chip
        # elif next frame is not avaliable, return None
    ''')
    print("请重写需要的所有帧:D\033[0m\n")
    return None

# 芯片逻辑的默认帧
# 如果如果芯片没有特意设定指定帧(比如2)
# 在运行到帧(2)的时候会默认调用此帧
# 对于一些需要实时更新输入输出的芯片
# 可以将主要逻辑都写在这里面
@Chip.keyframe(-1)
def _frame(chip):
    return chip

# 芯片帧定义的时候不需要特别注意帧函数的名字
# 但是推荐使用_frame来作为帧函数的名字
# _frame在被包装后，会把函数交给类，而本身得到None

# 在编写_frame函数的时候，请注意他的返回值
# 当你希望它在下一帧时也被更新时，你可以让它返回chip
# 如果你不希望它再进入更新流，你可以让它返回None
# 如果返回的是None，那么在下一轮更新开始之前，这个芯片将被忽略


# module test
# 你可以在模块中写一些小的测试
# 用来测试模块的可用性
# 我这里让它输出我设置的重写逻辑提示
if __name__ == "__main__":
    c = Chip(2,0,1)
    for i in range(c.maxframe+1) : 
        c.update(i)
        print(c)